//-----------------------------------------------------------------------

// <copyright file="FieldDefinitionRegistry.cs" company="Jamie Knight">

// Copyright (c) Jamie Knight. All rights reserved.

// </copyright>

//-----------------------------------------------------------------------

using DevKnightlie.WPF.AutoForm.FieldDefinitions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DevKnightlie.WPF.AutoForm
{
    /// <summary>
    /// Maintains a registry of property types against FieldDefinition objects
    /// </summary>
    public class FieldDefinitionRegistry
    {
        #region Singleton
        private static FieldDefinitionRegistry _default = new FieldDefinitionRegistry(true);

        public static FieldDefinitionRegistry Default
        {
            get
            {
                return _default;
            }
        }

        #endregion

        #region Private Fields

        private const String DEFAULT_KEY = "@@default";
        private Dictionary<Type, Dictionary<String, Type>> _registry = new Dictionary<Type, Dictionary<string, Type>>();


        #endregion

        #region Constructor

        /// <summary>
        /// Construct a new FieldDefinitionRegistry
        /// </summary>
        /// <param name="isDefault">Indicates whether the Singleton object is being created - if so, defaults are registered</param>
        protected FieldDefinitionRegistry(bool isDefault = false)
        {
            if (isDefault)
            {
                RegisterDefaults();
            }
        }

        /// <summary>
        /// Creates a new instance of FieldDefinitonRegistry
        /// </summary>
        /// <returns></returns>
        public static FieldDefinitionRegistry Create()
        {
            return new FieldDefinitionRegistry(false);
        }

        #endregion


        #region Methods

        /// <summary>
        /// Returns the default field D for properties of the given type
        /// </summary>
        /// <param name="T">Type to obtain</param>
        /// <returns>BaseFieldDefinition</returns>
        public BaseFieldDefinition Definition(Type T)
        {
            Type defType = null;
            if (_registry.ContainsKey(T))
            {
                Dictionary<String, Type> dict = _registry[T];
                if (dict.ContainsKey(DEFAULT_KEY))
                {
                    defType = dict[DEFAULT_KEY];
                }
            }
            object instance = null;

            if (defType != null)
            {
                instance = Activator.CreateInstance(defType);
            }
            return instance as BaseFieldDefinition;
        }


        public BaseFieldDefinition Definition<T>()
        {
            return Definition(typeof(T));
        }


        /// <summary>
        /// Returns an instance of the field definition registered for properties with the given name and type
        /// </summary>
        /// <param name="T">Property type</param>
        /// <param name="propertyName">Property name</param>
        /// <returns>BaseFieldDefinition</returns>
        public BaseFieldDefinition Definition(Type T, String propertyName)
        {
            Type defType = null;
            BaseFieldDefinition result = null;
            if (_registry.ContainsKey(T))
            {
                Dictionary<String, Type> dict = _registry[T];
                if (dict.ContainsKey(propertyName))
                {
                    defType = dict[propertyName];
                }
                else
                {
                    // Check for any wildcard property names
                    IEnumerable<String> preWildcards = dict.Keys.Where(s => s.StartsWith("*"));
                    foreach (String key in preWildcards)
                    {
                        if (propertyName.EndsWith(key.Substring(1)))
                        {
                            defType = dict[key];
                            break;
                        }
                    }
                    if (defType == null)
                    {
                        IEnumerable<String> postWildcards = dict.Keys.Where(s => s.EndsWith("*"));
                        foreach (String key in postWildcards)
                        {
                            if (propertyName.StartsWith(key.Substring(0, key.Length-1)))
                            {
                                defType = dict[key];
                                break;
                            }
                        }
                    }
                    if (defType == null && dict.ContainsKey(DEFAULT_KEY))
                    {
                        defType = dict[DEFAULT_KEY];
                    }
                }
            }
            if (defType != null)
            {
                result = Activator.CreateInstance(defType) as BaseFieldDefinition;

            }
            return result;
        }


        public BaseFieldDefinition Definition<T>(String propertyName)
        {
            return Definition(typeof(T), propertyName);
        }

        /// <summary>
        /// Sets the default field D for properties of the given type
        /// </summary>
        /// <param name="T"></param>
        /// <param name="fieldDefinition"></param>
        public void RegisterDefinition(Type T, Type definition)
        {
            if (definition != typeof(BaseFieldDefinition))
            {
                throw new ArgumentException("Definition must be of type BaseFieldDefinition", "definition");
            }
            Dictionary<String, Type> dict = null;
            if (!_registry.ContainsKey(T))
            {
                dict = new Dictionary<string, Type>();
                _registry[T] = dict;
            }
            else
            {
                dict = _registry[T];
            }
            dict[DEFAULT_KEY] = definition;
        }


        /// <summary>
        /// Sets the default field D for properties of the given type
        /// </summary>
        /// <param name="T"></param>
        /// <param name="fieldDefinition"></param>
        public void RegisterDefinition<T, D>()
        {
            if (!typeof(D).IsSubclassOf(typeof(BaseFieldDefinition)))
            {
                throw new ArgumentException("Definition must be of type BaseFieldDefinition", "definition");
            }
            Dictionary<String, Type> dict = null;
            if (!_registry.ContainsKey(typeof(T)))
            {
                dict = new Dictionary<string, Type>();
                _registry[typeof(T)] = dict;
            }
            else
            {
                dict = _registry[typeof(T)];
            }
            dict[DEFAULT_KEY] = typeof(D);
        }


        /// <summary>
        /// Sets the field D for properties of the given type with the given name
        /// </summary>
        /// <param name="T"></param>
        /// <param name="propertyName"></param>
        /// <param name="D"></param>
        public void RegisterDefinition(Type T, String propertyName, Type definition)
        {
            if (!definition.IsSubclassOf(typeof(BaseFieldDefinition)))
            {
                throw new ArgumentException("Definition must be of type BaseFieldDefinition", "definition");
            }
            Dictionary<String, Type> dict = null;
            if (!_registry.ContainsKey(T))
            {
                dict = new Dictionary<string, Type>();
                _registry[T] = dict;
            }
            else
            {
                dict = _registry[T];
            }
            dict[propertyName] = definition;
        }



        /// <summary>
        /// Sets the field D for properties of the given type with the given name
        /// </summary>
        /// <param name="T"></param>
        /// <param name="propertyName"></param>
        /// <param name="D"></param>
        public void RegisterDefinition<T, D>(String propertyName)
        {
            if (!typeof(D).IsSubclassOf(typeof(BaseFieldDefinition)))
            {
                throw new ArgumentException("Definition must be of type BaseFieldDefinition", "definition");
            }
            Dictionary<String, Type> dict = null;
            if (!_registry.ContainsKey(typeof(T)))
            {
                dict = new Dictionary<string, Type>();
                _registry[typeof(T)] = dict;
            }
            else
            {
                dict = _registry[typeof(T)];
            }
            dict[propertyName] = typeof(D);
        }



        private void RegisterDefaults()
        {
            if (_registry.Count > 0) return;
            RegisterDefinition<String, TextFieldDefinition>();
            RegisterDefinition<int, IntegerFieldDefinition>();
            RegisterDefinition<DateTime, DateFieldDefinition>();
            RegisterDefinition<bool, CheckBoxFieldDefinition>();
            RegisterDefinition<float, FloatFieldDefinition>();
            RegisterDefinition<double, FloatFieldDefinition>();
        }

        #endregion

    }
}
